package de.tu_darmstadt.gdi1.battleship.model;

import java.io.BufferedReader;
import java.io.IOException;
import java.util.Vector;

import de.tu_darmstadt.gdi1.battleship.exceptions.BoatFailException;
import de.tu_darmstadt.gdi1.battleship.exceptions.FieldAlreadyShotException;
import de.tu_darmstadt.gdi1.battleship.exceptions.InvalidCoordinatesException;
import de.tu_darmstadt.gdi1.battleship.exceptions.NumberFailException;

public class SingleMap {
	private ExtendedField[][] map;

	// verbleibende Schiffteile
	private int leftParts;
	// alle Schiffe
	Vector<Ship> allShips = new Vector<Ship>();

	public SingleMap() {
		map = new ExtendedField[10][10];
		leftParts = 30;
		clear();
	}

	public SingleMap(ExtendedField[][] f) {
		map = f;
		leftParts = countLeftParts();
	}

	/**
	 * counts the left parts of a ship
	 * 
	 * @return the value of left ship parts
	 */

	public int countLeftParts() {
		int counter = 0;
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				char ch = map[x][y].getSymbol();
				switch (ch) {
				case 'l':
					counter++;
					break;
				case 'r':
					counter++;
					break;
				case 't':
					counter++;
					break;
				case 'b':
					counter++;
					break;
				case 'h':
					counter++;
					break;
				case 'v':
					counter++;
					break;
				default:
					;
				}
			}
		}
		return counter;
	}

	public int getLeftParts() {
		return leftParts;
	}

	public void setLeftParts(int leftParts) {
		this.leftParts = leftParts;
	}

	public ExtendedField[][] getMap() {
		return map;
	}

	public void setMap(ExtendedField[][] f) {
		map = f;
	}

	public void setEntry(int x, int y, ExtendedField f) {
		Coordinates co;
		try {
			co = new Coordinates(x, y);
		} catch (InvalidCoordinatesException e) {
			return;
		}
		setEntry(co, f);
	}

	public ExtendedField getEntry(int x, int y) {
		return map[x][y];
	}

	public void setEntry(Coordinates co, ExtendedField f) {
		map[co.getX()][co.getY()] = f;
	}

	public ExtendedField getEntry(Coordinates co) {
		return map[co.getX()][co.getY()];
	}

	/**
	 * changes the map after a shot
	 * 
	 * @param coord
	 *            shot coordinates
	 * @throws InvalidCoordinatesException
	 * @throws FieldAlreadyShotException
	 */
	public void shootAt(Coordinates coord) throws InvalidCoordinatesException,
			FieldAlreadyShotException {
		int x = coord.getX();
		int y = coord.getY();
		shootAt(x, y);
	}

	/**
	 * changes the map after a shot permlock is true for the cpu
	 * 
	 * @param x
	 *            x coordinate of hit field
	 * @param y
	 *            y coordinate of hit field
	 * @throws InvalidCoordinatesException
	 * @throws FieldAlreadyShotException
	 */
	public void shootAt(int x, int y) throws InvalidCoordinatesException,
			FieldAlreadyShotException {

		if (x >= 0 && y >= 0 && x < 10 && y < 10) {
			char ch = map[x][y].getSymbol();
			switch (ch) {
			case '-':
				map[x][y].setSymbol('*');
				break;
			case 'l':
				map[x][y].setSymbol('L');
				leftParts--;
				break;
			case 'r':
				map[x][y].setSymbol('R');
				leftParts--;
				break;
			case 't':
				map[x][y].setSymbol('T');
				leftParts--;
				break;
			case 'b':
				map[x][y].setSymbol('B');
				leftParts--;
				break;
			case 'h':
				map[x][y].setSymbol('H');
				leftParts--;
				break;
			case 'v':
				map[x][y].setSymbol('V');
				leftParts--;
				break;
			default:
				throw new FieldAlreadyShotException("");
			}

			map[x][y].setAmmunition(0);

			Coordinates co = new Coordinates(x, y);
			Ship s = getShip(co);
			testShipSunk(s);

		} else
			throw new InvalidCoordinatesException("Cannot shoot outside field");

	}

	/**
	 * Generates a char-view of the map, where everything is visible.
	 * 
	 * @return Array containing symbols of the map.
	 */
	public char[][] getCharView() {
		// for easier creation of the View
		char[][] charView = new char[10][10];

		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				charView[x][y] = map[x][y].getSymbol();
			}
		}
		return charView;
	}

	/**
	 * Generates a char-view of the map, where only things are visible that are
	 * already hit.
	 * 
	 * @return Array containing symbols of the map as the enemy is allowed to
	 *         see.
	 * @throws InvalidCoordinatesException
	 */
	public char[][] getEnemyView() {
		char[][] enemyView = new char[10][10];

		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				Character sym = map[x][y].getSymbol();
				try {
					Coordinates co = new Coordinates(x, y);
					Ship s = getShip(co);
					if (s == null || !s.isSunk()) {
						switch (sym) {

						// Darf der Gegner nicht sehen.
						case 'l':
						case 'r':
						case 't':
						case 'b':
						case 'v':
						case 'h':
						case '-':
							enemyView[x][y] = 'f';
							break;

						// Darf Gegner nur als Treffer sehen.
						case 'L':
						case 'R':
						case 'T':
						case 'B':
						case 'V':
						case 'H':
							enemyView[x][y] = '#';
							break;
						default:
							enemyView[x][y] = sym;

						}
					} else {
						// Darf Gegner sehen.
						enemyView[x][y] = sym;
					}
				} catch (InvalidCoordinatesException e) {
					System.err.println("This should never happen.");
				}
			}
		}
		return enemyView;

	}

	/**
	 * deletes the temporary lock on all fields
	 */
	public void unlockTemp() {
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				map[x][y].setTempLock(false);
			}
		}
	}

	/**
	 * deletes the permanent lock on all fields
	 */
	public void unlockPerm() {
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				map[x][y].setPermLock(false);
			}
		}
	}

	/**
	 * changes every field into water
	 */
	public void clear() {
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				map[x][y] = new ExtendedField('-');
			}
		}
	}

	/**
	 * places a ship on a map, changes therefor every extended field wich is
	 * overwritten by the new ship
	 * 
	 * @param x
	 *            x coorinate of the first ship field
	 * @param y
	 *            y coordinate of the first ship field
	 * @param length
	 *            length of the ship
	 * @param v
	 *            true if the ship is vertical
	 */
	public void placeShip(int x, int y, int length, boolean v) {
		if (v) {
			// Anfang
			map[x][y].setSymbol('t');
			map[x][y].setAmmunition(5);
			// Mitte
			for (int yn = y + 1; yn < y + length - 1; yn++) {
				map[x][yn].setSymbol('v');
				map[x][yn].setAmmunition(5);
			}
			// Ende
			map[x][y + length - 1].setSymbol('b');
			map[x][y + length - 1].setAmmunition(5);
		} else {
			map[x][y].setSymbol('l');
			map[x][y].setAmmunition(5);
			for (int xn = x + 1; xn < x + length - 1; xn++) {
				map[xn][y].setSymbol('h');
				map[xn][y].setAmmunition(5);
			}
			map[x + length - 1][y].setSymbol('r');
			map[x + length - 1][y].setAmmunition(5);
		}

		// sperren der Felder
		pLockFields(x, y, length, v);
	}

	/**
	 * @param c
	 *            starting coordinate
	 * @param length
	 * @param v
	 *            true if the ship is vertical
	 * @see de.tu_darmstadt.gdi1.battleship.model.SingleMap#placeShip(int, int,
	 *      int, boolean)
	 */
	public void placeShip(Coordinates c, int length, boolean v) {
		placeShip(c.getX(), c.getY(), length, v);
	}

	/**
	 * locks all fields around a ship 
	 * @param x
	 *            starting x coordinate
	 * @param y
	 *            starting y coordinate
	 * @param length
	 *         length of the ship
	 * @param v
	 *            true if vertical
	 */
	public void pLockFields(int x, int y, int length, boolean v) {

		int startx = (x != 0) ? x - 1 : x;
		int starty = (y != 0) ? y - 1 : y;
		int endx = (x + length == 10) ? x + length - 1 : x + length;
		int endy = (y + length == 10) ? y + length - 1 : y + length;

		if (v) {
			for (int xl = startx; xl <= x + 1 && xl < 10; xl++) {
				for (int yl = y; yl < y + length && yl < 10; yl++) {
					map[xl][yl].setPermLock(true);
				}
			}
			map[x][starty].setPermLock(true);
			map[x][endy].setPermLock(true);
		} else {
			for (int xl = x; xl < x + length && xl < 10; xl++) {
				for (int yl = starty; yl <= y + 1 && yl < 10; yl++) {
					map[xl][yl].setPermLock(true);
				}
			}
			map[startx][y].setPermLock(true);
			map[endx][y].setPermLock(true);
		}
	}

	/**
	 * retrieves all unlocked fields
	 * 
	 * @return 
	 * array of coordinates
	 */
	public Coordinates[] getUnlockedFields() {
		Coordinates[] coords = new Coordinates[100];
		int count = 0;
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				if (!map[x][y].isPermLock() && !map[x][y].isTempLock()) {
					try {
						coords[count] = new Coordinates(x, y);
						count++;
					} catch (InvalidCoordinatesException e) {
						e.printStackTrace();
					}
				}
			}
		}
		// adjust length
		Coordinates[] fCoords = new Coordinates[count];
		for (int i = 0; i < count; i++) {
			fCoords[i] = coords[i];
		}

		return fCoords;
	}

	/**
	 * indicates at which position a new ship can't be placed
	 * 
	 * @param length
	 *            Length of the ship
	 * @param v
	 *            true if vertical
	 *            
	 */

	public void tLockFields(int length, boolean v) {
		// sperren wg Schiffen
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				// feld belegt
				if (map[x][y].isPermLock())
					// vertikal / horizontal
					if (v) {
						for (int i = y; i > y - length && i >= 0; i--) {
							map[x][i].setTempLock(true);
						}
					} else
						for (int i = x; i > x - length && i >= 0; i--) {
							map[i][y].setTempLock(true);
						}
			}
		}

		// sperren wg Feldrand
		if (v) {
			for (int i = 9; i > 9 - length + 1; i--) {
				for (int x = 0; x < 10; x++) {
					map[x][i].setTempLock(true);
				}
			}
		} else {
			for (int i = 9; i > 9 - length + 1; i--) {
				for (int y = 0; y < 10; y++) {
					map[i][y].setTempLock(true);
				}
			}
		}
	}

	/**
	 *sets for all fields the  temporal lock on true
	 */
	public void lockTemp() {
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				getEntry(x, y).setTempLock(true);
			}
		}

	}

	/**
	 * sets for all fields the  permanent lock on true
	 */
	public void lockPerm() {
		for (int x = 0; x < 10; x++) {
			for (int y = 0; y < 10; y++) {
				getEntry(x, y).setPermLock(true);
			}
		}

	}

	/**
	 * creates a random map
	 * 
	 * @return eine zufaellig erstellte Karte
	 * @throws NumberFailException
	 * @throws BoatFailException
	 */
	public void generateRandomMap() throws BoatFailException,
			NumberFailException {
		RandomMapGenerator rmg = new RandomMapGenerator();
		rmg.createNewRandomMap();
		map = rmg.getMap().getMap();
	}

	// Georgs Abteilung

	/**
	 * tests if a ship is sunken ?? 
	 * @param ship
	 *            the ship to be tested
	 * @throws InvalidCoordinatesException
	 */

	public void testShipSunk(Ship ship) throws InvalidCoordinatesException {
		if (ship != null) {
			int x, y, xn, yn, l;
			l = ship.getLength();
			xn = x = ship.getStart().getX();
			yn = y = ship.getStart().getY();
			while (xn < 10 && yn < 10 && getEnemyView()[xn][yn] == '#')
				if (ship.getVertical()) {
					if (yn - y + 1 == l) {
						ship.setSunk(true);
					}
					yn++;
				} else {
					if (xn - x + 1 == l) {
						ship.setSunk(true);
					}
					xn++;
				}
		}
	}

	/**
	 * tests if the map is valid
	 * 
	 * @return boolean, true if valid
	 */
	public boolean validmap() {
		int water = 0;

		for (int i = 0; i < 10; i++) { // x
			for (int j = 0; j < 10; j++) { // y
				if (getMap()[i][j].getSymbol() == '-'
						|| getMap()[i][j].getSymbol() == '*') {
					water++;
				}
			}
		}

		try {
			localizeShips();
			return (water == 70);
		} catch (BoatFailException bfe) {
			return false;
		} catch (NumberFailException nfe) {
			return false;
		}
	}

	/**
	 * creates a array of all ships on the map
	 * 
	 * @throws BoatFailException
	 * @throws NumberFailException
	 * 
	 * 
	 */

	// Yannic: Ich habe hier mal allships aus der Funktion geholt und in einen
	// Vector umgeschrieben.

	public void localizeShips() throws BoatFailException, NumberFailException {

		int z;
		int two = 0, three = 0, four = 0, five = 0;

		Coordinates co;
		allShips = new Vector<Ship>(10);

		unlockPerm();

		for (int i = 0; i < 10; i++) { // x
			for (int j = 0; j < 10; j++) { // y
				if ((getMap()[i][j].getSymbol() == 't' || getMap()[i][j]
						.getSymbol() == 'T')
						&& !getMap()[i][j].isPermLock()) {

					z = j + 1;

					while (!(getMap()[i][z].getSymbol() == 'b' || getMap()[i][z]
							.getSymbol() == 'B')) { // zaehlen der
						// Laenge bis b
						// erreicht is
						if (z > 9
								|| getMap()[i][z].isPermLock()
								|| z >= j + 5
								|| !(getMap()[i][z].getSymbol() == 'v' || getMap()[i][z]
										.getSymbol() == 'V'))
							throw new BoatFailException("");
						z++;
					}
					if (!getMap()[i][z].isPermLock())
						try {
							co = new Coordinates(i, j);
							Ship s = new Ship(co, z - j + 1, true);// schiff
							pLockFields(i, j, z - j + 1, true); // erstellen
							allShips.add(s);
							testShipSunk(s);
							switch (z - j + 1) {
							case 2:
								two++;
								break;
							case 3:
								three++;
								break;
							case 4:
								four++;
								break;
							case 5:
								five++;
								break;

							}
						} catch (InvalidCoordinatesException e) {
						}
				}

				if ((getMap()[i][j].getSymbol() == 'l' || getMap()[i][j]
						.getSymbol() == 'L')
						&& !getMap()[i][j].isPermLock()) {

					z = i + 1;

					while (!(getMap()[z][j].getSymbol() == 'r' || getMap()[z][j]
							.getSymbol() == 'R')) {
						if (z > 9
								|| z == i + 5
								|| getMap()[z][j].isPermLock()
								|| !(getMap()[z][j].getSymbol() == 'h' || getMap()[z][j]
										.getSymbol() == 'H'))
							throw new BoatFailException("");
						z++;
					}
					if (!getMap()[z][j].isPermLock())
						try {
							co = new Coordinates(i, j);
							Ship s = new Ship(co, z - i + 1, false);// schiff
							// erstellen
							allShips.add(s);
							testShipSunk(s);
							switch (z - i + 1) {
							case 2:
								two++;
								break;
							case 3:
								three++;
								break;
							case 4:
								four++;
								break;
							case 5:
								five++;
								break;

							}

						} catch (InvalidCoordinatesException e) {
						}
				}
			}
		}
		unlockPerm();
		if (five != 1 || four != 2 || three != 3 || two != 4)
			throw new NumberFailException("");
	}

	/**
	 * returns the ship that is located on the Coordinates. If given Coordinates
	 * point to water null returns null.
	 * 
	 * @param Coordinates
	 *            check coordinates 
	 * @return the ship on given Coordinates. null if water.
	 */
	public Ship getShip(Coordinates coordinates) {

		if (allShips == null) {
			System.err
					.println("No Ships on map? Please load level! Error in SingleMap.getShip().");
			return null;
		}

		for (Ship s : allShips) {
			// war noetig da contains nur voellig identische Coords akzeptiert,
			// sie haben jedoch unterschiedliche IDs
			for (Coordinates co : s.getCoords()) {
				if (co.equals(coordinates))
					return s;
			}
		}

		// Kein Schiff enthaelt die Koordinaten --> null
		return null;

	}
/**
 * generates save string
 * @return
 */
	public String generateSaveString() {
		StringBuffer sb = new StringBuffer("#SM\n");

		// leftParts beim loaden berechnen!!!

		sb.append(map.length).append('\n');
		sb.append(map[0].length).append('\n');

		for (int x = 0; x < map.length; x++) {
			for (int y = 0; y < map[0].length; y++) {
				sb.append(map[x][y].generateSaveString());
			}
		}

		sb.append(allShips.size()).append('\n');

		for (Ship s : allShips) {
			sb.append(s.generateSaveString());
		}

		return sb.toString();
	}
 /**
  * reads a single map save string
  * @param br
  * BufferedReader
  * @throws IOException
  */
	public void loadSaveString(BufferedReader br) throws IOException {

		if (br.readLine().equals("#SM")) {
			int x = Integer.valueOf(br.readLine());
			int y = Integer.valueOf(br.readLine());

			for (int i = 0; i < x; i++) {
				for (int j = 0; j < y; j++) {
					ExtendedField ef = new ExtendedField(' ');
					ef.loadSaveString(br);
					map[i][j] = ef;
				}
			}
			int l = Integer.valueOf(br.readLine());
			allShips = new Vector<Ship>(l);
			for (int i = 0; i < l; i++) {
				Ship s = new Ship();
				s.loadSaveString(br);
				allShips.add(s);
			}
		} else {
			System.err.println("Buffered Reader does not begin with #SM");
		}
	}
}
